<?php
class system_image_thumbGd extends system_image_abstract {
	protected $oldImage;
	protected $workingImage;
	protected $currentDimensions;
	protected $newDimensions;
	protected $options;
	protected $maxWidth;
	protected $maxHeight;
	protected $percent; 
	
	public function __construct($fileName, $options = array(), $isDataStream = false) {
		parent::__construct($fileName, $isDataStream);
		$this->determineFormat();		
		if($this->isDataStream === false) {
			$this->verifyFormatCompatiblity();
		}
		$this->loadImage();		
		$this->setOptions($options);	
		// TODO: Port gatherImageMeta to a separate function that can be called to extract exif data
	}
	
	public function __destruct() {
		if(is_resource($this->oldImage)) {
			imagedestroy($this->oldImage);
		}
		
		if(is_resource($this->workingImage)) {
			imagedestroy($this->workingImage);
		}
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function loadImage() {
		@ini_set('gd.jpeg_ignore_warning', 1);
		switch($this->format) {
			case 'GIF':
				$this->oldImage = imagecreatefromgif($this->fileName);
				break;
			case 'JPG':
				$this->oldImage = imagecreatefromjpeg($this->fileName);
				break;
			case 'PNG':
				$this->oldImage = imagecreatefrompng($this->fileName);
				break;
			case 'STRING':
				$this->oldImage = imagecreatefromstring($this->fileName);
				break;
		}
		$this->currentDimensions = array('width' => imagesx($this->oldImage), 'height' => imagesy($this->oldImage));
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function pad($width, $height, $color = array(255, 255, 255)) {
		// no resize - woohoo!
		if($width == $this->currentDimensions['width'] && $height == $this->currentDimensions['height']) {
			return $this;
		}		
		// create the working image
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($width, $height);
		} else {
			$this->workingImage = imagecreate($width, $height);
		}		
		// create the fill color
		$fillColor = imagecolorallocate($this->workingImage, $color[0], $color[1], $color[2]);
		// fill our working image with the fill color
		imagefill($this->workingImage, 0, 0, $fillColor);		
		// copy the image into the center of our working image
		imagecopyresampled(
			$this->workingImage, 
			$this->oldImage, 
			intval(($width - $this->currentDimensions['width']) / 2), 
			intval(($height - $this->currentDimensions['height']) / 2), 
			0, 0, 
			$this->currentDimensions['width'], 
			$this->currentDimensions['height'], 
			$this->currentDimensions['width'], 
			$this->currentDimensions['height']
		);
		
		// update all the variables and resources to be correct
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $width;
		$this->currentDimensions['height'] = $height;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function resize($maxWidth = 0, $maxHeight = 0) {
		if(!is_numeric($maxWidth)) {
			throw new InvalidArgumentException('$maxWidth must be numeric');
		}		
		if(!is_numeric($maxHeight)) {
			throw new InvalidArgumentException('$maxHeight must be numeric');
		}		
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($maxHeight) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $maxHeight;
			$this->maxWidth = (intval($maxWidth) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $maxWidth;
		} else {
			$this->maxHeight = intval($maxHeight);
			$this->maxWidth = intval($maxWidth);
		}		
		$this->calcImageSize($this->currentDimensions['width'], $this->currentDimensions['height']);		
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		} else {
			$this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		}		
		$this->preserveAlpha();		
		imagecopyresampled(
			$this->workingImage, 
			$this->oldImage, 
			0, 0, 0, 0, 
			$this->newDimensions['newWidth'], 
			$this->newDimensions['newHeight'], 
			$this->currentDimensions['width'], 
			$this->currentDimensions['height']
		);		
		// update all the variables and resources to be correct
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->newDimensions['newWidth'];
		$this->currentDimensions['height'] = $this->newDimensions['newHeight'];		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function adaptiveResize($width, $height) {
		if((!is_numeric($width) || $width == 0) && (!is_numeric($height) || $height == 0)) {
			throw new InvalidArgumentException('$width and $height must be numeric and greater than zero');
		}
		
		if(!is_numeric($width) || $width == 0) {
			$width = ($height * $this->currentDimensions['width']) / $this->currentDimensions['height'];
		}
		
		if(!is_numeric($height) || $height == 0) {
			$height = ($width * $this->currentDimensions['height']) / $this->currentDimensions['width'];
		}
		
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}
		
		$this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']);
		
		// resize the image to be close to our desired dimensions
		$this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		
		// reset the max dimensions...
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}
		
		// create the working image
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight);
		} else {
			$this->workingImage = imagecreate($this->maxWidth, $this->maxHeight);
		}
		
		$this->preserveAlpha();
		
		$cropWidth = $this->maxWidth;
		$cropHeight = $this->maxHeight;
		$cropX = 0;
		$cropY = 0;
		
		// now, figure out how to crop the rest of the image...
		if($this->currentDimensions['width'] > $this->maxWidth) {
			$cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2);
		} elseif($this->currentDimensions['height'] > $this->maxHeight) {
			$cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2);
		}
		
		imagecopyresampled($this->workingImage, $this->oldImage, 0, 0, $cropX, $cropY, $cropWidth, $cropHeight, $cropWidth, $cropHeight);
		
		// update all the variables and resources to be correct
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->maxWidth;
		$this->currentDimensions['height'] = $this->maxHeight;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function adaptiveResizePercent($width, $height, $percent = 50) {
		// make sure our arguments are valid
		if(!is_numeric($width) || $width == 0) {
			throw new InvalidArgumentException('$width must be numeric and greater than zero');
		}
		
		if(!is_numeric($height) || $height == 0) {
			throw new InvalidArgumentException('$height must be numeric and greater than zero');
		}
		
		// make sure we're not exceeding our image size if we're not supposed to
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}
		
		$this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']);
		
		// resize the image to be close to our desired dimensions
		$this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		
		// reset the max dimensions...
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}
		
		// create the working image
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight);
		} else {
			$this->workingImage = imagecreate($this->maxWidth, $this->maxHeight);
		}
		
		$this->preserveAlpha();
		
		$cropWidth = $this->maxWidth;
		$cropHeight = $this->maxHeight;
		$cropX = 0;
		$cropY = 0;
		
		// Crop the rest of the image using the quadrant
		

		if($percent > 100) {
			$percent = 100;
		} elseif($percent < 1) {
			$percent = 1;
		}
		
		if($this->currentDimensions['width'] > $this->maxWidth) {
			// Image is landscape
			$maxCropX = $this->currentDimensions['width'] - $this->maxWidth;
			$cropX = intval(($percent / 100) * $maxCropX);		
		} elseif($this->currentDimensions['height'] > $this->maxHeight) {
			// Image is portrait
			$maxCropY = $this->currentDimensions['height'] - $this->maxHeight;
			$cropY = intval(($percent / 100) * $maxCropY);		
		}
		
		imagecopyresampled($this->workingImage, $this->oldImage, 0, 0, $cropX, $cropY, $cropWidth, $cropHeight, $cropWidth, $cropHeight);		
		// update all the variables and resources to be correct
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->maxWidth;
		$this->currentDimensions['height'] = $this->maxHeight;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function adaptiveResizeQuadrant($width, $height, $quadrant = 'C') {
		if(!is_numeric($width) || $width == 0) {
			throw new InvalidArgumentException('$width must be numeric and greater than zero');
		}		
		if(!is_numeric($height) || $height == 0) {
			throw new InvalidArgumentException('$height must be numeric and greater than zero');
		}		
		// make sure we're not exceeding our image size if we're not supposed to
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}		
		$this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']);
		
		// resize the image to be close to our desired dimensions
		$this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		
		// reset the max dimensions...
		if($this->options['resizeUp'] === false) {
			$this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height;
			$this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width;
		} else {
			$this->maxHeight = intval($height);
			$this->maxWidth = intval($width);
		}
		
		// create the working image
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight);
		} else {
			$this->workingImage = imagecreate($this->maxWidth, $this->maxHeight);
		}
		$this->preserveAlpha();		
		$cropWidth = $this->maxWidth;
		$cropHeight = $this->maxHeight;
		$cropX = 0;
		$cropY = 0;
		
		if($this->currentDimensions['width'] > $this->maxWidth) {
			// Image is landscape
			switch($quadrant) {
				case 'L':
					$cropX = 0;
					break;
				
				case 'R':
					$cropX = intval(($this->currentDimensions['width'] - $this->maxWidth));
					break;
				
				case 'C':
				default:
					$cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2);
					break;
			}		
		} elseif($this->currentDimensions['height'] > $this->maxHeight) {
			// Image is portrait
			switch($quadrant) {
				case 'T':
					$cropY = 0;
					break;
				
				case 'B':
					$cropY = intval(($this->currentDimensions['height'] - $this->maxHeight));
					break;
				
				case 'C':
				default:
					$cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2);
					break;
			}		
		}		
		imagecopyresampled($this->workingImage, $this->oldImage, 0, 0, $cropX, $cropY, $cropWidth, $cropHeight, $cropWidth, $cropHeight);		
		// update all the variables and resources to be correct
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->maxWidth;
		$this->currentDimensions['height'] = $this->maxHeight;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function resizePercent($percent = 0) {
		if(!is_numeric($percent)) {
			throw new InvalidArgumentException('$percent must be numeric');
		}
		
		$this->percent = intval($percent);
		
		$this->calcImageSizePercent($this->currentDimensions['width'], $this->currentDimensions['height']);
		
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		} else {
			$this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']);
		}
		
		$this->preserveAlpha();
		
		ImageCopyResampled($this->workingImage, $this->oldImage, 0, 0, 0, 0, $this->newDimensions['newWidth'], $this->newDimensions['newHeight'], $this->currentDimensions['width'], $this->currentDimensions['height']);
		
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->newDimensions['newWidth'];
		$this->currentDimensions['height'] = $this->newDimensions['newHeight'];		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function cropFromCenter($cropWidth, $cropHeight = null) {
		if(!is_numeric($cropWidth)) {
			throw new InvalidArgumentException('$cropWidth must be numeric');
		}		
		if($cropHeight !== null && !is_numeric($cropHeight)) {
			throw new InvalidArgumentException('$cropHeight must be numeric');
		}		
		if($cropHeight === null) {
			$cropHeight = $cropWidth;
		}		
		$cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth;
		$cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight;
		
		$cropX = intval(($this->currentDimensions['width'] - $cropWidth) / 2);
		$cropY = intval(($this->currentDimensions['height'] - $cropHeight) / 2);		
		$this->crop($cropX, $cropY, $cropWidth, $cropHeight);		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function crop($startX, $startY, $cropWidth, $cropHeight) {
		// validate input
		if(!is_numeric($startX)) {
			throw new InvalidArgumentException('$startX must be numeric');
		}		
		if(!is_numeric($startY)) {
			throw new InvalidArgumentException('$startY must be numeric');
		}		
		if(!is_numeric($cropWidth)) {
			throw new InvalidArgumentException('$cropWidth must be numeric');
		}		
		if(!is_numeric($cropHeight)) {
			throw new InvalidArgumentException('$cropHeight must be numeric');
		}		
		// do some calculations
		$cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth;
		$cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight;		
		// ensure everything's in bounds
		if(($startX + $cropWidth) > $this->currentDimensions['width']) {
			$startX = ($this->currentDimensions['width'] - $cropWidth);
		
		}		
		if(($startY + $cropHeight) > $this->currentDimensions['height']) {
			$startY = ($this->currentDimensions['height'] - $cropHeight);
		}		
		if($startX < 0) {
			$startX = 0;
		}		
		if($startY < 0) {
			$startY = 0;
		}		
		// create the working image
		if(function_exists('imagecreatetruecolor')) {
			$this->workingImage = imagecreatetruecolor($cropWidth, $cropHeight);
		} else {
			$this->workingImage = imagecreate($cropWidth, $cropHeight);
		}		
		$this->preserveAlpha();		
		imagecopyresampled($this->workingImage, $this->oldImage, 0, 0, $startX, $startY, $cropWidth, $cropHeight, $cropWidth, $cropHeight);		
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $cropWidth;
		$this->currentDimensions['height'] = $cropHeight;		
		return $this;
	}
	
	/**
	 * @retunrn system_image_GdThumb
	 */
	public function rotateImage($direction = 'CW') {
		if($direction == 'CW') {
			$this->rotateImageNDegrees(90);
		} else {
			$this->rotateImageNDegrees(-90);
		}		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function rotateImageNDegrees($degrees) {
		if(!is_numeric($degrees)) {
			throw new InvalidArgumentException('$degrees must be numeric');
		}		
		if(!function_exists('imagerotate')) {
			throw new RuntimeException('Your version of GD does not support image rotation.');
		}		
		$this->workingImage = imagerotate($this->oldImage, $degrees, 0);		
		$newWidth = $this->currentDimensions['height'];
		$newHeight = $this->currentDimensions['width'];
		$this->oldImage = $this->workingImage;
		$this->currentDimensions['width'] = $newWidth;
		$this->currentDimensions['height'] = $newHeight;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function imageFilter($filter, $arg1 = false, $arg2 = false, $arg3 = false, $arg4 = false) {
		if(!is_numeric($filter)) {
			throw new InvalidArgumentException('$filter must be numeric');
		}		
		if(!function_exists('imagefilter')) {
			throw new RuntimeException('Your version of GD does not support image filters.');
		}		
		$result = false;
		if($arg1 === false)
			$result = imagefilter($this->oldImage, $filter);
		else 
			if($arg2 === false)
				$result = imagefilter($this->oldImage, $filter, $arg1);
			else 
				if($arg3 === false)
					$result = imagefilter($this->oldImage, $filter, $arg1, $arg2);
				else 
					if($arg4 === false)
						$result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3);
					else
						$result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3, $arg4);
		
		if(!$result) {
			throw new RuntimeException('GD imagefilter failed');
		}		
		$this->workingImage = $this->oldImage;		
		return $this;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function show($rawData = false) {
		if(headers_sent() && php_sapi_name() != 'cli') {
			throw new RuntimeException('Cannot show image, headers have already been sent');
		}		
		// When the interlace option equals true or false call imageinterlace else leave it to default
		if($this->options['interlace'] === true)
			imageinterlace($this->oldImage, 1);
		elseif($this->options['interlace'] === false)
			imageinterlace($this->oldImage, 0);
		
		switch($this->format) {
			case 'GIF':
				if($rawData === false) {
					header('Content-type: image/gif');
				}
				imagegif($this->oldImage);
				break;
			case 'JPG':
				if($rawData === false) {
					header('Content-type: image/jpeg');
				}
				imagejpeg($this->oldImage, null, $this->options['jpegQuality']);
				break;
			case 'PNG':
			case 'STRING':
				if($rawData === false) {
					header('Content-type: image/png');
				}
				imagepng($this->oldImage);
				break;
		}		
		return $this;
	}
	
	public function getImageAsString() {
		$data = null;
		ob_start();
		$this->show(true);
		$data = ob_get_contents();
		ob_end_clean();		
		return $data;
	}
	
	/**
	 * @return system_image_GdThumb
	 */
	public function save($fileName, $format = null) {
		$validFormats = array('GIF', 'JPG', 'PNG');
		$format = ($format !== null) ? strtoupper($format) : $this->format;		
		if(!in_array($format, $validFormats)) {
			throw new InvalidArgumentException('Invalid format type specified in save function: ' . $format);
		}		
		if(!is_writeable(dirname($fileName))) {
			if($this->options['correctPermissions'] === true) {
				@chmod(dirname($fileName), 0777);
				if(!is_writeable(dirname($fileName))) {
					throw new RuntimeException('File is not writeable, and could not correct permissions: ' . $fileName);
				}
			} else {
				throw new RuntimeException('File not writeable: ' . $fileName);
			}
		}		
		// When the interlace option equals true or false call imageinterlace else leave it to default
		if($this->options['interlace'] === true)
			imageinterlace($this->oldImage, 1);
		elseif($this->options['interlace'] === false)
			imageinterlace($this->oldImage, 0);
		
		switch($format) {
			case 'GIF':
				imagegif($this->oldImage, $fileName);
				break;
			case 'JPG':
				imagejpeg($this->oldImage, $fileName, $this->options['jpegQuality']);
				break;
			case 'PNG':
				imagepng($this->oldImage, $fileName);
				break;
		}		
		return $this;
	}
	
	/**
	 * @return system_image_thumbGd
	 */
	public function setOptions($options = array()) {
		if(!is_array($this->options)) {
			$this->options = array();
		}
		if(!is_array($options)) {
			throw new InvalidArgumentException('setOptions requires an array');
		}
		if(sizeof($this->options) == 0) {
			$defaultOptions = array(
				'resizeUp' => false, 
				'jpegQuality' => 100, 
				'correctPermissions' => false, 
				'preserveAlpha' => true, 
				'alphaMaskColor' => array(255, 255, 255), 
				'preserveTransparency' => true, 
				'transparencyMaskColor' => array(0, 0, 0), 
				'interlace' => null
			);
		} else {
			$defaultOptions = $this->options;
		}		
		$this->options = array_merge($defaultOptions, $options);
		return $this;
	}
	public function getOptions() {
		return $this->options;
	}
		
	/**
	 * @return system_image_thumbGd
	 */
	public function setCurrentDimensions($currentDimensions) {
		$this->currentDimensions = $currentDimensions;
		return $this;
	}
	public function getCurrentDimensions() {
		return $this->currentDimensions;
	}
	
	/**
	 * @return system_image_thumbGd
	 */
	public function setMaxHeight($maxHeight) {
		$this->maxHeight = $maxHeight;
		return $this;
	}
	public function getMaxHeight() {
		return $this->maxHeight;
	}
		
	/**
	 * @return system_image_thumbGd
	 */
	public function setMaxWidth($maxWidth) {
		$this->maxWidth = $maxWidth;
		return $this;
	}
	public function getMaxWidth() {
		return $this->maxWidth;
	}
		
	/**
	 * @return system_image_thumbGd
	 */
	public function setNewDimensions($newDimensions) {
		$this->newDimensions = $newDimensions;
		return $this;
	}
	public function getNewDimensions() {
		return $this->newDimensions;
	}
	
	/**
	 * @return system_image_thumbGd
	 */
	public function setPercent($percent) {
		$this->percent = $percent;
		return $this;
	}
	public function getPercent() {
		return $this->percent;
	}
		
	/**
	 * @return system_image_thumbGd
	 */
	public function setOldImage($oldImage) {
		$this->oldImage = $oldImage;
		return $this;
	}
	public function getOldImage() {
		return $this->oldImage;
	}
		
	/**
	 * @return system_image_thumbGd
	 */
	public function setWorkingImage($workingImage) {
		$this->workingImage = $workingImage;
		return $this;
	}
	public function getWorkingImage() {
		return $this->workingImage;
	}
	
	protected function calcWidth($width, $height) {
		$newWidthPercentage = (100 * $this->maxWidth) / $width;
		$newHeight = ($height * $newWidthPercentage) / 100;		
		return array('newWidth' => intval($this->maxWidth), 'newHeight' => intval($newHeight));
	}
	
	protected function calcHeight($width, $height) {
		$newHeightPercentage = (100 * $this->maxHeight) / $height;
		$newWidth = ($width * $newHeightPercentage) / 100;		
		return array('newWidth' => ceil($newWidth), 'newHeight' => ceil($this->maxHeight));
	}
	
	protected function calcPercent($width, $height) {
		$newWidth = ($width * $this->percent) / 100;
		$newHeight = ($height * $this->percent) / 100;		
		return array('newWidth' => ceil($newWidth), 'newHeight' => ceil($newHeight));
	}
	
	protected function calcImageSize($width, $height) {
		$newSize = array('newWidth' => $width, 'newHeight' => $height);		
		if($this->maxWidth > 0) {
			$newSize = $this->calcWidth($width, $height);			
			if($this->maxHeight > 0 && $newSize['newHeight'] > $this->maxHeight) {
				$newSize = $this->calcHeight($newSize['newWidth'], $newSize['newHeight']);
			}
		}		
		if($this->maxHeight > 0) {
			$newSize = $this->calcHeight($width, $height);
			
			if($this->maxWidth > 0 && $newSize['newWidth'] > $this->maxWidth) {
				$newSize = $this->calcWidth($newSize['newWidth'], $newSize['newHeight']);
			}
		}		
		$this->newDimensions = $newSize;
	}
	
	protected function calcImageSizeStrict($width, $height) {
		// first, we need to determine what the longest resize dimension is..
		if($this->maxWidth >= $this->maxHeight) {
			// and determine the longest original dimension
			if($width > $height) {
				$newDimensions = $this->calcHeight($width, $height);
				
				if($newDimensions['newWidth'] < $this->maxWidth) {
					$newDimensions = $this->calcWidth($width, $height);
				}
			} elseif($height >= $width) {
				$newDimensions = $this->calcWidth($width, $height);
				
				if($newDimensions['newHeight'] < $this->maxHeight) {
					$newDimensions = $this->calcHeight($width, $height);
				}
			}
		} elseif($this->maxHeight > $this->maxWidth) {
			if($width >= $height) {
				$newDimensions = $this->calcWidth($width, $height);
				
				if($newDimensions['newHeight'] < $this->maxHeight) {
					$newDimensions = $this->calcHeight($width, $height);
				}
			} elseif($height > $width) {
				$newDimensions = $this->calcHeight($width, $height);
				
				if($newDimensions['newWidth'] < $this->maxWidth) {
					$newDimensions = $this->calcWidth($width, $height);
				}
			}
		}		
		$this->newDimensions = $newDimensions;
	}
	
	protected function calcImageSizePercent($width, $height) {
		if($this->percent > 0) {
			$this->newDimensions = $this->calcPercent($width, $height);
		}
	}
	
	protected function determineFormat() {
		if($this->isDataStream === true) {
			$this->format = 'STRING';
			return;
		}		
		$formatInfo = getimagesize($this->fileName);		
		// non-image files will return false
		if($formatInfo === false) {
			if($this->remoteImage) {
				$this->triggerError('Could not determine format of remote image: ' . $this->fileName);
			} else {
				$this->triggerError('File is not a valid image: ' . $this->fileName);
			}			
			// make sure we really stop execution
			return;
		}
		
		$mimeType = isset($formatInfo['mime']) ? $formatInfo['mime'] : null;
		
		switch($mimeType) {
			case 'image/gif':
				$this->format = 'GIF';
				break;
			case 'image/jpeg':
				$this->format = 'JPG';
				break;
			case 'image/png':
				$this->format = 'PNG';
				break;
			default:
				$this->triggerError('Image format not supported: ' . $mimeType);
		}
	}
	
	protected function verifyFormatCompatiblity() {
		$isCompatible = true;
		$gdInfo = gd_info();		
		switch($this->format) {
			case 'GIF':
				$isCompatible = $gdInfo['GIF Create Support'];
				break;
			case 'JPG':
				$isCompatible = (isset($gdInfo['JPG Support']) || isset($gdInfo['JPEG Support'])) ? true : false;
				break;
			case 'PNG':
				$isCompatible = $gdInfo[$this->format . ' Support'];
				break;
			default:
				$isCompatible = false;
		}		
		if(!$isCompatible) {
			// one last check for "JPEG" instead
			$isCompatible = $gdInfo['JPEG Support'];			
			if(!$isCompatible) {
				$this->triggerError('Your GD installation does not support ' . $this->format . ' image types');
			}
		}
	}
	
	protected function preserveAlpha() {
		if($this->format == 'PNG' && $this->options['preserveAlpha'] === true) {
			imagealphablending($this->workingImage, false);			
			$colorTransparent = imagecolorallocatealpha($this->workingImage, $this->options['alphaMaskColor'][0], $this->options['alphaMaskColor'][1], $this->options['alphaMaskColor'][2], 0);
			imagefill($this->workingImage, 0, 0, $colorTransparent);
			imagesavealpha($this->workingImage, true);
		}
		// preserve transparency in GIFs... this is usually pretty rough tho
		if($this->format == 'GIF' && $this->options['preserveTransparency'] === true) {
			$colorTransparent = imagecolorallocate($this->workingImage, $this->options['transparencyMaskColor'][0], $this->options['transparencyMaskColor'][1], $this->options['transparencyMaskColor'][2]);
			imagecolortransparent($this->workingImage, $colorTransparent);
			imagetruecolortopalette($this->workingImage, true, 256);
		}
	}
}
